
jsal.module({
    name   : 'heist.runtime',
    onload : function () {
		var main = this;
        if (! this.heist) this.heist = {};
        var heist = this.heist;

       /**
        * Namespace: heist.runtime - Runtime component of Heist template system.
        *
		* Classes for running templates.
        */

        heist.runtime = {};

	   /**
		* Private function: compFromJs
		*
		* Turn a compiled component into a Component object.
		*
		* Arguments:
		*   js - (required string) the Javascript for the compiled template.
		*
		* Returns: a Component object.
		*/

		heist.runtime.compFromJs = function (js) {
			var func,
				comp = new heist.runtime.Component();

			// extra function call here works around bare anonymous function
			// throwing a syntax error, at least in ff - strange
//			try {
			    func = main.eval('(function(){return ' + js + '})()');
//			}
//			catch (e) {
//				alert(e + ' :\n' + js);
//			}
			func(comp);
			return comp;
		};

	   /**
		* Class: Context
		*
		* Manages the context of a template when it's running, including collecting the output.
		*
		* Constructor arguments:
		*   component - (required Component) the component that is being rendered.
		*/

		var Context = function (component) {
			this._contents = [];
			this.component = component;
		};

	   /**
		* Method: append
		*
		* Append content to the output that is being generated.
		*
		* Arguments:
		*   c - (required string) the content that is being added.
		*/
		Context.prototype.append = function (c) {
			this._contents.push(c);
		}

	   /**
		* Method: content
		*
		* Get the content that has been generated.
		*
		* Returns: string.		
		*/

		Context.prototype.contents = function () {
			return this._contents.join('');
		};

	   /**
		* Method: comp
		*
		* Run a method on the context's component.
		*
		* Arguments:
		*   name - (optional string) the name of the method to call. If not set then the entire component is run.
		*/

		Context.prototype.comp = function (name) {
			// don't shift arguments here because it messes name var up in sm
			if (name) {
				var meth = this.component.getMethod(name);
				Array.prototype.shift.call(arguments);
				meth.apply(this, arguments);
			}
			else {
				Array.prototype.shift.call(arguments);
				var meths = this.component.defaultMethods();
				for (var i = 0, l = meths.length; i < l; i++)
					meths[i].apply(this, arguments);
			}
		};

	   /**
		* Class: Component
		*
		* A compiled in memory template.
		*/

		var Component = heist.runtime.Component = function () {
			this.methods = {};
		};

	   /**
		* Private method: addDefaultMethod
		*
		* Set the function that renders the content outside of methods in the file - the default method.
		*
		* Arguments: TODO
		*/

		Component.prototype.addDefaultMethod = function (func) {
			this.defaultMethod = func;
		};

		Component.prototype.addMethod = function (name, func) {
			this.methods[name] = func;
		};

		Component.prototype.setParentComp = function (comp) {
			this.parentComp = comp;
		};

		Component.prototype.getMethod = function (name) {
			var meth = this.methods[name];
			if (! meth && this.parentComp)
			    meth = this.parentComp.getMethod(name);
			if (! meth)
				throw 'unknown method \'' + name + '\'';
			return meth;
		};

		Component.prototype.defaultMethods = function () {
			var comp = this;
			var meths = [];
			while (comp) {
				meths.unshift(comp.defaultMethod);
				comp = comp.parentComp;
			}
			return meths;
		};

		Component.prototype.run = function (name) {
			var context = new Context(this);
			context.comp.apply(context, arguments);
			return context.contents();
		};

		var Root = function (path) {
			this.path = path;
		};

		var Handler = heist.runtime.Handler = function (opts) {
			if (! opts.roots)
				throw 'roots is a required option';
			if (typeof(opts.roots) == 'string')
				this.roots = [opts.roots];
			else
				this.roots = opts.roots.slice();
			for (var i = 0, l = this.roots.length; i < l; i++)
				this.roots[i] = new Root(this.roots[i]);
		};

		Handler.prototype.load = function (path, fallback, func, invocant) {
			var roots = this.roots.slice(),
				iterator = function () {
					if (! roots.length) return;
					var root = roots.shift();
					root.load(path, fallback, iterator, func, invocant);
				};
			iterator();
		};


		Root.prototype.loadSource = function (path, next, func, invocant) {
			get(this.path + path, function (source) {
				if (! source) next();
				console.log('-----------------> found source');
			});
		};

		Root.prototype.loadCompiled = function (path, next, fallback, func, invocant) {
			get(this.path + path + 'c', function (code) {
				if (! code) {
					if (fallback)
						this.loadSource(path, next, func, invocant);
					else
						next();
				}
				console.log('-----------------> found compiled code');
			});
		};

		Root.prototype.load = function (path, fallback, next, callback, invocant) {
			if (fallback == null)
				// load the source no matter what
				this.loadSource(path, next, callback, invocant);
			else
				// load compiled and fallback to source if requested
				this.loadCompiled(path, next, fallback, callback, invocant);
		};

		Handler.prototype.sourceUrls = function (path) {
			var urls = [], roots = this.roots;
			for (var i = 0, len = roots.length; i < len; i++) {
				urls[i] = roots[i].path + path;
			}
			return urls;
		};

		function xhr () {
			if (main.XMLHttpRequest)
				return xhr = function () { return new XMLHttpRequest(); }();
			var activeXType, ieXhr, res;
			if (main.ActiveXObject) {
				for (activeXType in { 'MSXML2.XMLHTTP.6.0' : 1, '"Msxml2.XMLHTTP.3.0"' : 1 }) {
					ieXhr = function () { new ActiveXObject(activeXType) };
					try {
						var res = ieXhr();
						xhr = ieXhr;
						return res;
					} catch (e) {}
				}
			}
		}

		function get (url, callback) {
			var request = xhr();
			console.log('request = ' + request);
			request.open('GET', url, true);
			request.onreadystatechange = function () {
				if (request.readyState != 4) return;
				if (request.status != 200 || request.status == 0 && ! request.responseText) {
					// TODO replace this with hook in for logging, etc.
					console.log('could not load ' + url + ' - status: ' + status);
					callback();
				}
				else
					callback(request.responseText);
			};
		}

		Handler.prototype.loadSource = function (path, func, invocant) {
			var url = this.sourceUrl(path);
			get(url, function (code) {
				throw 'TODO - load source';
			});
		};

		Handler.prototype.loadCompiled = function (path, func, invocant) {
			var url = this.compiledUrl(path);
			get(url, function (code) {
				throw 'TODO - load compiled';
			});
		};


//        heist.runtime.Handler = function () {
//             var Root = function (ident) {
//                 this.ident = ident;
//                 this.components = {};
//             };
            
//             Root.prototype.add_component = function (comp) {
//                 this.components[comp.path] = comp;
//             };

//             var Component = function (path, root, code) {
//                 this.path = path;
//                 this.root = root;
//                 this.code = code;
//             };

//             var Handler = function () {
//                 this.roots = [new Root('main')];
//                 this.components = {};
//             };

//             Handler.prototype.get_root = function (ident) {
//                 for (var i = 0, len = this.roots.length; i < len; i++) {
//                     if (this.roots[i].ident == ident) return this.roots[i];
//                 }
//                 return undefined;
//             };

//             Handler.prototype.add_component = function (comp) {
//                 comp = comp(Component);
//                 var root = this.get_root(comp.root);
//                 if (! root) throw 'no component root ' + comp.root;
//                 root.add_component(comp);
//             };

//             Handler.prototype.run = function (path) {
                
//             };

//             return Handler;
//        }();
	}
});

